/*
————————————————————————————————————————————————————————————————————————————————————
｜ 本文件用于实现请求资源的选择
｜ 设计思路：
｜ 1. 请求题库：返回题库的Json串
｜ 2. 请求题目内容：返回题目内容的Json串
｜ 3. 提交代码：将用户提交的代码与测试用例整合，发送给编译运行服务器，接收返回结果的Json串，返回给用户
————————————————————————————————————————————————————————————————————————————————————
*/
#pragma once

#include <cassert>

#include <iostream>
#include <string>
#include <mutex>
#include <fstream>
#include <atomic>

#include <jsoncpp/json/json.h>

#include "../Comm/httplib.h"
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../Comm/Comm.hpp"
#include "../Comm/Log.hpp"

namespace ns_control
{
    const std::string server_machine_conf = "./machine/server_machine.conf";

    // 先描述 -> 让我们更好的知道主机的相关内容
    class Machine
    {
    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mutex(nullptr)
        {
        }
        Machine(const std::string &ip, const int port)
            : _ip(ip), _port(port), _load(0), _mutex(new std::mutex())
        {
        }
        ~Machine() {}

    public:
        // 增加负载数
        void IncLoad()
        {
            if (_mutex)
                _mutex->lock();
            {
                ++_load;
            }
            if (_mutex)
                _mutex->unlock();
        }
        // 减少负载数
        void DecLoad()
        {
            if (_mutex)
                _mutex->lock();
            {
                --_load;
            }
            if (_mutex)
                _mutex->unlock();
        }

        // 清空负载数
        void ClearLoad()
        {
            if (_mutex)
                _mutex->lock();
            {
                _load = 0;
            }
            if (_mutex)
                _mutex->unlock();
        }

        void Print()
        {
            std::cout << "[ip : " << _ip << "][port : " << _port << "][_load : " << _load << "]" << std::endl;
        }

    public:
        std::string _ip;    // 主机ip
        int _port;          // 主机唯一端口号
        uint64_t _load;     // 负载情况
        std::mutex *_mutex; // 锁，这里锁对象是禁止copy的，所以使用指针
    };

    // 负载均衡模块
    class LoadBlance
    {
    public:
        LoadBlance()
        {
            assert(LoadConf());
            _log.LogMessage(ClassFile, Info, "加载所有编译服务器主机信息成功\n");
        }
        ~LoadBlance() {}

    private:
        // 加载编译服务器的主机配置文件
        bool LoadConf()
        {
            std::ifstream in(server_machine_conf);
            if (!in.is_open())
            {
                _log.LogMessage(ClassFile, Error, "加载编译服务器主机信息失败\n");
                return false;
            }
            // 加载成功，拿出主机内容
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> content_group;
                FileOper::File::SepListLine(line, ":", content_group);
                if (content_group.size() != 2)
                {
                    _log.LogMessage(ClassFile, Warning, "加载部分主机内容失败\n");
                    continue;
                }

                _online.push_back(_machines.size());
                _machines.push_back({content_group[0], std::stoi(content_group[1])});
            }
            in.close();
            return true;
        }

    public:
        // 智能选择负载少的主机
        bool SmartChoice(int &id, Machine *&machine)
        {
            // 加锁
            _mutex.lock();
            {
                int online_num = _online.size();

                if (online_num == 0)
                {
                    _mutex.unlock();
                    _log.LogMessage(ClassFile, Error, "所有编译服务器都已经挂掉了!!\n");
                    return false;
                }
                else
                {
                    // 一定有一个台主机可以提供编译运行服务,选择负载最小的一台主机
                    id = _online[0];
                    machine = &_machines[_online[0]];
                    int min_load = _machines[_online[0]]._load;
                    // 遍历所有在线主机，选择负载最小的主机
                    for (int i = 1; i < online_num; ++i)
                    {
                        int cur_load = _machines[_online[i]]._load;
                        if (min_load > cur_load)
                        {
                            min_load = cur_load;
                            id = _online[i];
                            machine = &_machines[_online[i]];
                        }
                    }
                }
            }
            // 解锁
            _mutex.unlock();
            return true;
        }

        // 离线id对应的主机
        void OffLineMachine(int id)
        {
            _mutex.lock();
            {
                for(auto iter = _online.begin(); iter != _online.end(); ++iter)
                {
                    if(*iter == id)
                    {
                        _machines[id].ClearLoad();
                        _online.erase(iter);
                        _offline.push_back(id); //用id是因为online删除iter位置的值后，iter就会指向下一个元素
                        break;
                    }
                }
            }
            _mutex.unlock();
        }

        // 上线所有离线的主机
        /***************************************
         * 设计思路：我们一定是在外部给主机重新上线
         * 再通过发送信号的方式，让程序内部捕捉信号
         * 完成自定义方法 -> 上线所有离线的主机
        ***************************************/
        void Online()
        {
            _mutex.lock();
            {
                // 上线所有离线的主机
                _online.insert(_online.end(), _offline.begin(), _offline.end());
                // 清空离线主机列表
                _offline.erase(_offline.begin(), _offline.end());
            }
            _mutex.unlock();
        }

        void DebugOnLineAndOffLine()
        {
            _mutex.lock();
            {
                std::cout << "在线主机列表 : ";
                for(auto& e : _online)
                {
                    std::cout << e << " ";
                }
                std::cout << std::endl;

                std::cout << "离线主机列表 : ";
                for(auto& e : _offline)
                {
                    std::cout << e << " ";
                }
                std::cout << std::endl;
            }
            _mutex.unlock();
        }

        void DebugMachineList()
        {
            std::cout << "所有主机列表 : "  << std::endl;
            for(auto& e : _machines)
            {
                e.Print();
            }
        }

    private:
        std::vector<Machine> _machines; // 编译服务的主机列表
        std::vector<int> _online;       // 在线主机列表
        std::vector<int> _offline;     // 离线主机列表
        std::mutex _mutex;              // 一定会有成百上千台客户端来访问我们的选择主机部分，所以要加锁
    };

    // 根据不同请求来选择不同的功能
    class Control
    {
    public:
        Control() {}
        ~Control() {}

        // 一. 返回题库的网页信息
        bool Questions(std::string &html)
        {
            std::vector<ns_model::Question> vec_q;
            // 拿到题库信息
            if (!_model.GetAllQuestions(vec_q))
                return false;
            // 转换为网页信息
            _view.GetAllListHtml(vec_q, html);
            return true;
        }

        // 二. 返回指定题目内容的网页信息
        bool Question_Message(const std::string &question_number, std::string &html)
        {
            ns_model::Question q;
            // 拿到题目的属性
            if (!_model.GetQuestionMessage(question_number, q))
                return false;
            // 转换为网页信息
            _view.GetOneHtml(q, html);
            return true;
        }

    // 三. 返回题目编译结果的网页信息
        bool Result(const std::string &question_number, const std::string &req_json, std::string &result_json)
        {
            // 1. 将code与本地的测试用例结合

                // (1) 读取当前编号的题目的属性
                ns_model::Question q;
                if (!_model.GetQuestionMessage(question_number, q)) return false;
                // (2) 反序列化，拿到用户输入的代码 、自测用例（不考虑）
                Json::Value root;
                Json::Reader reader;
                reader.parse(req_json, root);
                std::string code = root["code"].asCString();
                std::string cin_nums = root["cin_nums"].asCString();
                // (3) 将code与本地的测试用例结合，形成完整代码
                Json::Value compile_root;
                compile_root["cin_nums"] = cin_nums;
                compile_root["code"] = code + q._tail;
                compile_root["cpu"] = q._cpu;
                compile_root["as"] = q._as;
                // (4) 序列化，至此完成要向编译运行服务器发送的内容
                Json::StyledWriter writer;
                std::string compile_json = writer.write(compile_root);

            // 2. 负载均衡式地选择编译运行服务器（选择负载最小的那一台，使用轮询法）
            /**********************************************************
            *  使用轮询法来选择负载最小的一个服务器，要保证的是一定能选择到，所以
            *  选择失败要继续去选择，除非所有的服务器都挂掉了 
            * **********************************************************/
            while (true)
            {
                Machine *machine = nullptr; // 选择的机器
                int id = -1;                 // 选择的机器id

                // 负载均衡选择一台主机，所有主机都挂掉了就失败，我们就无法选择
                if (!_load_blance.SmartChoice(id, machine)) break;
                
            // 3. 选择成功, 将完整代码发送给编译运行服务器，并接收响应信息

                httplib::Client client(machine->_ip, machine->_port);
                //增加选择主机的负载
                machine->IncLoad();
                _log.LogMessage(ClassFile, Info, "选择主机成功 [%d号主机][port:%d][负载为%d]\n", id, machine->_port, machine->_load);
                // 将内容发送给comile_run服务器
                auto res = client.Post("/compile_run", compile_json, "application/json;charset=utf-8"); 
                if(res) //发送成功
                {
                    //接收其响应信息，状态码必须是200，响应才是成功的，否则重新选择主机进行通信
                    if (res->status == 200)
                    {
                        result_json = res->body; // 拿到compile_run 返回的Json串
                        _log.LogMessage(ClassFile, Info, "[%d号主机][port:%d]请求编译服务成功\n", id, machine->_port);
                        machine->DecLoad();      // 服务结束，减少该主机的负载数
                        break;
                    }
                    else
                    {
                        _log.LogMessage(ClassFile, Info, "[%d号主机][port:%d]返回状态码为失败\n", id, machine->_port);
                        machine->DecLoad();
                    }
                }
                else  // 向对应主机发送失败，该主机出现问题，打印日志并将其离线
                {
                    machine->DecLoad(); //返回的状态码不是200，直接减少负载数，重新通信
                    _log.LogMessage(ClassFile, Error, "当前请求主机挂掉了 [id:%d][port:%d]\n", id, machine->_port);
                    _load_blance.OffLineMachine(id);
                    _load_blance.DebugOnLineAndOffLine();//查看在线主机和离线主机列表
                }
            }
            // _load_blance.DebugMachineList();
            // 4. 转换为html ??? need???

            return true;
        }

        void ReStart()
        {
            _load_blance.Online();
        }

    private:
        ns_model::Model _model;  // 负责拿到请求资源
        ns_view::View _view;     // 负责将请求资源转为html格式，完成渲染
        LoadBlance _load_blance; // 负责负载均衡式选择编译服务的主机
    };
};
